#pragma once 

#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include "Util.hpp"
#include <string>
#include <vector>
#include "Log.hpp"
#include <sstream>
#include <unordered_map>
#include <map>
#include <sys/stat.h>
#include <algorithm>
#include <sys/sendfile.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <stdlib.h>

#define sep ": "
#define WEB_ROOT "wwwroot"
#define HOME_PAGE "index.html"
#define HTTP_VERSION "HTTP/1.0"
#define LINE_END "\r\n"
#define PAGE_404 "404.html"

#define OK 200
#define NOT_FOUND 404
#define BAD_REQUEST 400
#define SERVER_ERROR 500

//该函数是将状态码转换成状态信息
static std::string Code2Desc(int status_code)
{
    std::string s;
    switch(status_code)
    {
      case 200:
          s = "OK";
          break;

      case 404:
          s = "NOT FOUND";
          break;

      default:
          break;
    }

    return s;
}

//该函数将该路径下要请求的文件的后缀转换成描述
static std::string Suffix2Desc(const std::string& suffix)
{ 
  //此处定义一个map用来表示文件后缀转换表
  static std::unordered_map<std::string , std::string> suffix2desc = {
      {".html" , "text/html"},
      {".css" , "text/css"},
      {".js" , "application/javascript"},
      {".jpg" , "application/x-jpg"},
      {".xml" , "application/xml"},
  }; 

  auto iter = suffix2desc.find(suffix);
  if (iter != suffix2desc.end())
  {//此时表示找到了
      return iter->second; //也就返回他的目标串
  }

  //走到这里表示没有找到后缀
  return "text/html"; //没有找到统一按照html来进行返回
}

//请求类
class HttpRequest
{
  public:
    std::string request_line; //请求行
    std::vector<std::string> request_header; //请求报头
    std::string blank; //空行
    std::string request_body; //请求正文

    //解析请求行完毕之后的结果
    std::string method; //方法
    std::string uri; //网址
    std::string version; //http版本号

    //解析报头完毕之后的结果
    std::unordered_map<std::string , std::string> header_kv; //用来将报头中的数据一一对应
    int content_length; //正文长度
    std::string path; //表示路径
    std::string suffix; //表示当前文件的后缀
    std::string query_string; //参数

    int size; //正文长度
    bool cgi; //是否执行cgi机制
  public:
    HttpRequest():content_length(0),cgi(false){}
    ~HttpRequest(){}
};

class HttpResponse
{
  public:
    std::string status_line; //状态行
    std::vector<std::string> response_header; //响应报头
    std::string blank; //空行
    std::string response_body; //响应正文

    //解析状态行的结果
    int status_code; //状态码
    int fd;
  public:
    HttpResponse():blank(LINE_END),status_code(OK),fd(-1){}
    ~HttpResponse(){}
};

//该类是用来读取请求，分析请求，构建响应
//IO通信
class EndPoint
{
  private:
    int sock; //通信套接字
    bool stop; //判断当前是否出现错误
    HttpRequest http_request; //请求类
    HttpResponse http_response; //响应类
  private:
    bool RecvHttpRequestLine()
    { //读取请求行
        auto &line = http_request.request_line;
        //此时判断读取请求行的时候是否出错
        if (Util::ReadLine(sock , line) > 0) //将请求行读取request_line中
        { //该函数返回值是读取数据后的长度
            line.resize(line.size() - 1); //此时将获取的请求行中的\n删除
            LOG(INFO , http_request.request_line);
        }
        
        else 
        {
            stop = true;
        }
        
        std::cout << "RecvHttpRequestLine: " << stop << std::endl;
        return stop;
    }
    
    //获取到报文头部
    bool RecvHttpRequestHeader()
    {
        std::string line;
        while (true)
        {
          line.clear();
          //判断报头读取是否出现错误
          if (Util::ReadLine(sock , line) <= 0)
          {//获得请求报头中的一行
              stop = true;
              break;
          }
          if (line == "\n") //此时表示读到了空行，将空行也赋值到响应空行中
          {
            http_request.blank = line;
            break;
          }
          line.resize(line.size() - 1); //将\n去掉
          http_request.request_header.push_back(line); //插入到容器中
          LOG(INFO , line);
        }
        
        std::cout << "stop debug: " << stop << std::endl;
        return stop;
    }
    
    //对请求行进行解析
    void ParseHttpRequestLine()
    {
        auto& line = http_request.request_line;
        std::stringstream ss(line); //这种方式可以从结构体转换成字符串
        //通过stringstream将请求行按照空格分割成方法，网址和http版本类型
        ss >> http_request.method >> http_request.uri >> http_request.version;
        auto& method = http_request.method;
        //避免方法类型是小写的，将收到的方法类型全部转换成大写的
        std::transform(method.begin() , method.end() , method.begin() , ::toupper);
        LOG(INFO , http_request.method);
        LOG(INFO , http_request.uri);
        LOG(INFO , http_request.version);
    }
    
    //对请求报头进行解析
    void ParseHttpRequestHeader()
    {
        for (auto& iter : http_request.request_header)
        {//遍历存储报头的vector
          std::string key;
          std::string value;
          if (Util::CutString(iter , key , value , sep))
          {
              //将报头中每行的数据提取出来，进行映射
              http_request.header_kv.insert({key , value});
          }
        }
    }

    //判断是否有正文,如果存在正文就找出正文长度
    bool IsNeedRecvHttpRequestBody()
    {
        auto& method = http_request.method;
        if (method == "POST")
        {//只有是POST方法才会有正文
           auto& header_kv =  http_request.header_kv;
           //通过map找到报头中Content-Length属性
           auto iter = header_kv.find("Content-Length");
           if (iter != header_kv.end())
           { //此时表示找到了
             LOG(INFO , "Post Method, Content-Length: " + iter->second);
             http_request.content_length = atoi(iter->second.c_str());
             return true;
           }
        }

        return false;
    }

    //读取请求行正文
    bool RecvHttpRequestBody()
    {
      if (IsNeedRecvHttpRequestBody())
      {//此时表示需要读取正文
          int content_length = http_request.content_length; //正文长度
          auto& body = http_request.request_body;

          char ch = 0;
          //因为之前空行都读取完了，就剩下正文了
          while (content_length)
          {
              //读取正文，每次读取一个字节
              ssize_t s = recv(sock , &ch , 1 , 0);
              if (s > 0)
              {
                  body.push_back(ch);
                  content_length --;
              }

              else 
              {
                  stop = true;
                  break;
              }
          }
          LOG(INFO , body);
      }

      return stop;
    }

    //执行CGI机制，也就是将参数传递给cgi程序中
    int ProcessCgi()
    {
        LOG(INFO , "cgi begin!");
        int code = OK; //状态信息
        //父进程数据
        //下面两个变量分别表示如果是GET方法就在?后面传递的参数
        //如果是POST方法就是正文传递的参数
        auto& method = http_request.method; //请求方法
        auto& query_string = http_request.query_string; //参数
        auto& body_text = http_request.request_body; //请求正文
        auto& bin = http_request.path; //因为此时可执行程序的路径就是该路径
        int content_length = http_request.content_length; //请求正文长度
        auto& response_body = http_response.response_body; //响应正文

        //要定义环境变量的原因是，我们要将我们的数据存储到环境变量中，这样才能避免exec替换我们的数据
        std::string query_string_env; //参数环境变量
        std::string method_env; //请求方法
        std::string content_length_env; //正文长度，这几个变量都要保存到环境变量中

        //站在父进程角度
        int input[2];
        int output[2];
        
        //创建两个管道
        if (pipe(input) < 0)
        {
            LOG(ERROR , "pipe input error!");
            code = SERVER_ERROR; //此处属于服务器错误
            return code;
        }

        if (pipe(output) < 0)
        {
            LOG(ERROR , "pipe  output error!");
            code = SERVER_ERROR;
            return code;
        }

        //新线程，但是从头到尾都只有一个进程，就是httpserver
        //所以这里创建一个子进程来进行程序替换
        pid_t pid = fork();
        if (pid == 0) 
        { //child
            //exec*函数
            close(input[0]);
            close(output[1]);
            
            //因为使用路径名 + 可执行程序也可以执行，所以第二个参数我们就直接用路径就可以了
            //替换成功之后，目标子进程如何得知对应的读写文件描述符是多少呢？？
            //程序替换只替换代码和数据而不替换进程相关的数据结构包括文件描述表
            //也就是当程序替换成功之后，之前在定义的input和output都被新的数据段所替换了，但是我们
            //打开的文件比如管道都还是存在的，所以我们就做一个约定，让目标被替换之后的进程，读取管道
            //等价于读取标准输入，写入管道，等价于写到标准输出，所以在替换之前就可以将两个管道重定向
            //到标准输入和标准输出中。
            
            //站在子进程角度
            //input[1]: 写入  -> 1 -> input[1]
            //output[0]: 读入 -> 0 -> output[0]
         
            method_env = "METHOD=";
            method_env += method;
            std::cout << "cgi: " << method_env << std::endl;
            putenv((char*)method_env.c_str()); //因为C++风格的c_str()是转换成const char*的，所以要强转一下

            if (method == "GET")
            {//此时表示方法为GET
                query_string_env = "QUERY_STRING=";
                query_string_env += query_string;
                putenv((char*)query_string_env.c_str()); //将参数加入到环境变量中，因为exec系列函数不会替换环境变量
                LOG(WARNING , "Get Method, Add Query_String Env");
            }
            
            else if (method == "POST")
            { //如果是POST方法就将正文长度放到环境变量中
                content_length_env = "CONTENT_LENGTH=";
                content_length_env += std::to_string(content_length);
                putenv((char*)content_length_env.c_str());
                LOG(INFO , "Post Method, Add Content_Length ENV");
            }
            
            else 
            {
                //do nothing
            }
            //此时是将0重定向到output[0]中，从标准输入中读就是从output[0]中读
            dup2(output[0] , 0); 
            dup2(input[1] , 1);  
         
            execl(bin.c_str() , bin.c_str() , NULL); //进行程序替换，将子进程替换为cgi程序
            
            exit(1); //此时表示程序替换失败了，就直接返回
        }   

        else if (pid < 0)
        {//fork error
            LOG(ERROR , "fork error!");
            return 404;
        }

        else 
        { //father
            //父进程要读
            close(input[1]);
            close(output[0]);

            if (method == "POST")
            { //此时父进程就将正文中的数据通过output[1]写到管道中
                const char* start = body_text.c_str(); //获取请求正文
                int total = 0; //表示你已经写了多少
                int ssize = 0; //你本次写了多少
                //此处while循环是为了防止一次写的内容没有写完，只写了一部分就把管道填满了，所以要循环写
                while (total < content_length && (ssize = write(output[1] , start + total , body_text.size() - total)) > 0)
                { //此处父进程通过管道将数据发送给子进程
                      total += ssize;
                }

                //父进程也可以通过环境变量传输数据，因为环境变量是具有全局属性的，也就是可以被子进程继承的，环境变量不受exec系列的程序替换的影响
            }

            //这里父进程要从管道中读数据，此时是将cgi程序执行的结果读取出来
            char ch = 0;
            while (read(input[0] , &ch , 1) > 0)
            {//此时将读到的数据放到响应正文中
                response_body.push_back(ch);
            }
            
            int status = 0;
            pid_t ret = waitpid(pid , &status , 0);
            if (ret == pid) //此时表示等待成功
            { 
                //分析退出码
                if (WIFEXITED(status))
                {
                    if(WEXITSTATUS(status) == 0)
                    {
                        code = OK;
                    }

                    else 
                    {
                      code = BAD_REQUEST; //请求异常
                    }
                }

                else 
                {
                    code = SERVER_ERROR; //此时表示不正确退出
                }
            }

            close(input[0]);
            close(output[1]);
        }
        return code;
    }

    //此方法为普通请求，不需要使用Cgi机制
    int ProcessNoneCgi()
    {
        http_response.fd = open(http_request.path.c_str() , O_RDONLY); //通过请求路径打开文件
        if (http_response.fd >= 0)
        { //打开成功
          LOG(INFO , http_request.path + "open success!");
          return OK; //此处表示打开文件成功
        }
        return NOT_FOUND;
    }
    
    //构建OK的响应
    void BuildOkResponse()
    {
        std::string line = "Content-Type: ";
        line += Suffix2Desc(http_request.suffix); //分析该文件的后缀,通过文件后缀找到文件
        line += LINE_END;
        http_response.response_header.push_back(line);
        line = "Content-Length: ";
        if (http_request.cgi)
        { //如果是以cgi机制来执行的，那么就返回正文长度
            line += std::to_string(http_response.response_body.size());
        }
        else 
        { //此时是非cgi机制时的正文长度
            line += std::to_string(http_request.size); //GET
        }
        line += LINE_END;
        http_response.response_header.push_back(line); //将分析的数据加入到报头中
    }

    //整理错误信息
    void HandlerError(std::string page)
    {
        http_request.cgi = false;
        //要给用户返回对应的404页面
        http_response.fd = open(page.c_str() , O_RDONLY); //以只读的形式打开该文件
        if (http_response.fd > 0)
        { //此时表示打开成功了
            struct stat st;
            stat(page.c_str() , &st); //通过stat函数将文件信息返回到st中
            http_request.size = st.st_size;
            //此处将响应报头插入进来
            std::string line = "Content-Type: text/html";
            line += LINE_END;
            http_response.response_header.push_back(line);
            line = "Content-Length: ";
            line += std::to_string(st.st_size);
            line += LINE_END;
            http_response.response_header.push_back(line);
        }
    }

    //构建状态行和响应报头  
    void BuildHttpResponseHelper()
    {
        auto& code = http_response.status_code;
        auto& status_line = http_response.status_line; //状态行
        status_line += HTTP_VERSION;
        status_line += " ";
        status_line += std::to_string(code); //将状态码转换成字符串
        status_line += " ";
        status_line += Code2Desc(code); //根据错误码转换成错误码描述
        status_line += LINE_END; //别忘记换行 
        //构建响应正文，可能包括响应报头
        std::string path = WEB_ROOT;
        path += "/";
        switch(code)
        {
            case OK:
              BuildOkResponse();
              break;
            case NOT_FOUND:
              path += PAGE_404;
              HandlerError(path); //没有找到页面
              break;
            case BAD_REQUEST:
              path += PAGE_404;
              HandlerError(path);
              break;
            case SERVER_ERROR:
              path += PAGE_404;
              HandlerError(path);
              break;
            default:
              break;
        }
    }

  public:
    EndPoint(int _sock)
        :sock(_sock),stop(false)
    {}
    
    bool IsStop()
    {
        return stop;
    }

    //读取请求
    void RecvHttpRequest()
    {
        if ((!RecvHttpRequestLine()) && (!RecvHttpRequestHeader()))
        { //此时表示读取请求行和报头都没有出错
            ParseHttpRequest();
        }
    }
    
    //分析请求
    void ParseHttpRequest()
    {
        ParseHttpRequestLine();
        ParseHttpRequestHeader();
        RecvHttpRequestBody(); //确认是否需要读取正文
    }

    //构建响应
    void BulidHttpResponse()
    {
        struct stat st; 
        std::string _path;
        std::size_t found = 0;
        auto& code = http_response.status_code; //获取状态码
        if (http_request.method != "GET" && http_request.method != "POST")
        { //非法请求
            LOG(WARNING , "method is not right");
            code = BAD_REQUEST; //此时报错为坏的请求 
            goto END;
        }
        if (http_request.method == "GET")
        {
            size_t pos = http_request.uri.find('?');
            if (pos != std::string::npos)
            {//此时表示找到了,表示是带参的就要提取
              Util::CutString(http_request.uri , http_request.path , http_request.query_string , "?");
              http_request.cgi = true; //GET方法如果带参也要使用cgi
            }

            else 
            {
              //如果没有参数，那么uri就是他的路径
              http_request.path = http_request.uri; //无参的时候就不需要执行cgi机制
            }
        }
        
        else if (http_request.method == "POST")
        {
            //POST
            http_request.cgi = true; //此时表示当方法是POST的时候就要使用cgi了
            http_request.path = http_request.uri;
        }

        else 
        {
          // Do Nothing
        }
        _path = http_request.path;
        http_request.path = WEB_ROOT;
        http_request.path += _path;
        
        if (http_request.path[http_request.path.size() - 1] == '/')
        { //此时判断该路径的最后一个元素是否是根目录，如果是就要加上首页信息,也就是根目录下的.html文件
            http_request.path += HOME_PAGE;            
        }

        if (stat(http_request.path.c_str() , &st) == 0)
        { //该函数用来判断当前路径是否存在
            //此时表示该资源路径存在
            if (S_ISDIR(st.st_mode)) //st_mode表示文件对应的模式
            {//此时说明请求资源是一个目录，不被允许的，需要做一下相关的处理
              //虽然是一个目录，但是绝对不会以/结尾
                http_request.path += "/";
                http_request.path += HOME_PAGE; //此时每个目录中都会有一个html文件，将其加上就可以了
                stat(http_request.path.c_str() , &st); //这里要对该路径进行重新解析
            }
            //此时分别进行判定，用户，所属组，其他这三个角色是否存在可执行权限，只要有一个角色存在可执行权限就可以认为该文件是可执行文件
            if ((st.st_mode & S_IXUSR) || (st.st_mode & S_IXGRP) || (st.st_mode & S_IXOTH)) //表示三个权限中有一个是1，就表示该文件是可执行文件
            {//该三个条件是用来表示目标文件是一个可执行文件
                //那么就要进行特殊处理
                ProcessCgi();
            } 

            http_request.size = st.st_size; //访问资源的大小

        }

        else 
        {//说明资源是不存在的
            std::string info = http_request.path;
            info += "Not Found!";
            LOG(WARNING , info);
            code = NOT_FOUND;
            goto END;
        }
        
        //此时path一定存在
        found = http_request.path.rfind("."); //表示从后面开始查找，直到找到.
        if (found == std::string::npos)
        { //此时表示没有找到
            http_request.suffix = ".html";
        }

        else 
        {
            http_request.suffix = http_request.path.substr(found); //从点的位置开始一直提取到结尾
        }

        if (http_request.cgi)
        {
            code = ProcessCgi(); //这里就要使用CGI的方式来进行处理数据
        }

        else 
        {
            //不是CGI也就是GET方法不带参数，那么也就是一个简单的网页返回
            code = ProcessNoneCgi(); //此时就不使用CGI的方式进行处理数据
        }
END:
        //状态行填充了，响应报头也有了，空行也有了，正文也有了
        BuildHttpResponseHelper(); 
    }

    void SendHttpResponse()
    {
      //将状态行发送过去
        send(sock , http_response.status_line.c_str() , http_response.status_line.size() , 0);
        for (auto& iter : http_response.response_header)
        { //将状态报头发送过去
            send(sock , iter.c_str() , iter.size() , 0);
        }
        //将空行发送过去
        send(sock , http_response.blank.c_str() , http_response.blank.size() , 0);
        
        if (http_request.cgi)
        {
            //如果是cgi则其响应正文在body中
            auto& response_body = http_response.response_body;
            int size = 0;
            int total = 0;
            const char* start = response_body.c_str();
            int x = response_body.size();
            while ( total < x && (size = send(sock , start + total , response_body.size() - total , 0)) > 0)
            {
                total += size;
            }
        }

        else 
        {
            std::cout << "...................." << http_response.fd << std::endl;
            std::cout << "...................." << http_request.size << std::endl;
            sendfile(sock , http_response.fd , nullptr , http_request.size); //此时sendfile函数是实现通过两个文件描述符来传递数据
            //sock表示要读取数据的文件描述符，fd表示待写入数据的文件描述符，此时fd已经打开并且已经写入数据，nullptr表示此时的偏移量是0，
            //也就是直接从文件起始位置读取，http_request.size表示要读取的长度，此处表示正文长度。
            close(http_response.fd); //关闭掉之前的文件描述符
        }
    }

    ~EndPoint()
    {
      close(sock);
    }
};

//#define DEBUG 1

class CallBack
{
    public:
      CallBack()
      {}

      void operator()(int sock)
      { //设置仿函数
          HandlerRequest(sock);
      }

      void HandlerRequest(int sock)
      {
          LOG(INFO , "Hander Request Begin");
      
#ifdef DEBUG //如果定义的DEBUG就执行下面
          //For Test
          char buffer[4096];
          recv(sock , buffer , sizeof(buffer) , 0);
          std::cout << "--------------------begin--------------" << std::endl;
          std::cout << buffer << std::endl;
          std::cout << "--------------------end----------------" << std::endl;
#else 
          EndPoint* ep = new EndPoint(sock);
          
          ep->RecvHttpRequest();
          if (!ep->IsStop())
          {
              LOG(INFO , "Recv No Error, Begin Build And Send!");
              ep->BulidHttpResponse();
              ep->SendHttpResponse();
          }

          else 
          {
              LOG(WARNING , "Recv Error, Stop Build And Send!");
          }
          delete ep;
#endif
          LOG(INFO , "Hander Request End");
      }

      ~CallBack(){}
};
